package cn.xinfei.xdecision.thread.monitor;

import cn.xinfei.xdecision.constant.GlobalConstant;
import cn.xinfei.xdecision.data.thread.AsyncExecuteThreadPool;
import cn.xinfei.xdecision.data.thread.TraceThreadPoolExecutor;
import cn.xinfei.xdecision.thread.CustomerThreadPoolExecutor;
import cn.xinfei.xdecision.thread.ThreadPoolExecutorConfig;
import com.alibaba.fastjson.JSONObject;
import io.micrometer.core.instrument.ImmutableTag;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

@Slf4j
@RefreshScope
@Component
public class ThreadPoolRegister {

    private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);

    @Autowired
    private ApplicationContext applicationContext;

    @Value("${xdecision.monitor.threadpool:5000}")
    private Integer threadMonitorPeriod;

    public static void registerThreadPool(String poolName, ThreadPoolExecutor executor) {
//        log.info("上报线程池监控,poolName={}", poolName);
        List<Tag> tags = Arrays.asList(new ImmutableTag("poolName", poolName));
        Metrics.gauge("thread_pool_core_size", tags, executor, ThreadPoolExecutor::getCorePoolSize);
        Metrics.gauge("thread_pool_largest_size", tags, executor, ThreadPoolExecutor::getLargestPoolSize);
        Metrics.gauge("thread_pool_max_size", tags, executor, ThreadPoolExecutor::getMaximumPoolSize);
        Metrics.gauge("thread_pool_active_size", tags, executor, ThreadPoolExecutor::getActiveCount);
        Metrics.gauge("thread_pool_thread_count", tags, executor, ThreadPoolExecutor::getPoolSize);
        Metrics.gauge("thread_pool_completed_count", tags, executor, ThreadPoolExecutor::getCompletedTaskCount);
        Metrics.gauge("thread_pool_task_count", tags, executor, ThreadPoolExecutor::getTaskCount);
        Metrics.gauge("thread_pool_queue_size", tags, executor.getQueue(), BlockingQueue::size);
        Metrics.gauge("thread_pool_queue_remaining", tags, executor.getQueue(), BlockingQueue::remainingCapacity);
    }

    public static void checkParamChange(ThreadPoolExecutorConfig config, ThreadPoolExecutor executor) {
        if (null != config) {
            int corePoolSize = config.getCorePoolSize();
            int maximumPoolSize = config.getMaximumPoolSize();
            int corePoolSizePre = executor.getCorePoolSize();
            int maximumPoolSizePre = executor.getMaximumPoolSize();

            if (corePoolSize != corePoolSizePre) {
                log.info("update corePoolSize from {} to {}", corePoolSizePre, corePoolSize);
                executor.setCorePoolSize(corePoolSize);
            }
            if (maximumPoolSize != maximumPoolSizePre) {
                log.info("update maximumPoolSize from {} to {}", maximumPoolSizePre, maximumPoolSize);
                executor.setMaximumPoolSize(maximumPoolSize);
            }
        }

    }

    @PostConstruct
    public void init() {
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                report();
            } catch (Exception e) {
                log.error("线程池监控异常", e);
            }
        },1,2, TimeUnit.SECONDS);
    }

    private void report() {
        MDC.put(GlobalConstant.NID, Thread.currentThread().getName());

        Map<String, TraceThreadPoolExecutor> threadPoolExecutorMap = AsyncExecuteThreadPool.threadPoolExecutorMap;
        log.debug("threadPoolExecutorMap size ={}, poolNames = {}", threadPoolExecutorMap.size(), JSONObject.toJSONString(threadPoolExecutorMap.keySet()));
        threadPoolExecutorMap.values().forEach(threadPool -> {
            ThreadPoolRegister.registerThreadPool(threadPool.getPoolName(), threadPool);
        });
        Map<String, CustomerThreadPoolExecutor> customerThreadPoolExecutorMap = CustomerThreadPoolExecutor.customerThreadPoolExecutorMap;
        log.debug("customerThreadPoolExecutorList size={}", customerThreadPoolExecutorMap.size());
        customerThreadPoolExecutorMap.entrySet().forEach(e -> {
            CustomerThreadPoolExecutor threadPoolExecutor = e.getValue();
            ThreadPoolRegister.registerThreadPool(threadPoolExecutor.getPoolName(), threadPoolExecutor);
        });
    }
}
